#include <jni.h>
#include <malloc.h>
#include <android/log.h>
#include "Export/common.h"
#include "Export/media.h"
#include "Util/logger.h"
#include "Util/onceToken.h"

using namespace std;
using namespace ZL::Util;

class AndroidLogoutChannel: public LogChannel {
public:
    AndroidLogoutChannel(const string &name, LogLevel level = LDebug) :LogChannel(name, level, "%Y-%m-%d %H:%M:%S"){}
    virtual ~AndroidLogoutChannel(){}
    void write(const LogInfo_ptr &logInfo){
        if (level() > logInfo->getLevel()) {
            return;
        }
        static android_LogPriority LogPriorityArr[10];
        static onceToken s_token([](){
            LogPriorityArr[LTrace] = ANDROID_LOG_VERBOSE;
            LogPriorityArr[LDebug] = ANDROID_LOG_DEBUG;
            LogPriorityArr[LInfo] = ANDROID_LOG_INFO;
            LogPriorityArr[LWarn] = ANDROID_LOG_WARN;
            LogPriorityArr[LError] = ANDROID_LOG_ERROR;
            LogPriorityArr[LFatal] = ANDROID_LOG_FATAL;
        }, nullptr);
        __android_log_print(LogPriorityArr[logInfo->level],"MEDIA_JNI","%s %s",logInfo->function.c_str(),logInfo->message.str().c_str());
    }
};

/*
 * 加载动态库
 */
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) {
    onAppStart();
    Logger::Instance().add(std::make_shared<AndroidLogoutChannel>("android",LTrace));
    return JNI_VERSION_1_6;
}

/*
 * 释放动态库
 */
JNIEXPORT void JNICALL JNI_OnUnload(JavaVM* vm, void* reserved){
    onAppExit();
}

#define JNI_API(retType,funName,...) extern "C"  JNIEXPORT retType Java_com_wing_tools_video_ZLMediaKit_##funName(JNIEnv* env, jclass cls,__VA_ARGS__)

string jstringTostring(JNIEnv* env, jstring jstr) {
    char* rtn = NULL;
    jclass clsstring = env->FindClass("java/lang/String");
    jmethodID mid = env->GetMethodID(clsstring, "getBytes", "()[B");
    jbyteArray barr = (jbyteArray) env->CallObjectMethod(jstr, mid);
    jsize alen = env->GetArrayLength(barr);
    jbyte* ba = (jbyte *) env->GetByteArrayElements(barr, JNI_FALSE);
    ba[alen] = 0;
    string ret = (char *) ba;
    env->ReleaseByteArrayElements(barr, ba, 0);
    /*局部变量其实在函数返回后会自动释放*/ \
    env->DeleteLocalRef(clsstring);
    return ret;
}

unsigned char * memfind(unsigned char *buf,unsigned long len,const char *subbuf,unsigned long sublen){
    for (unsigned long i=0;i<len-sublen;i++){
        if(memcmp((char *)(buf+i),subbuf,sublen)==0){
            return buf+i;
        }
    }
    return NULL;
}

void HandleH264(unsigned char *pEncodeBuffer,unsigned long dwBufferLen, std::function<void(unsigned char*,unsigned long)> cb){
    unsigned char *end,*start=pEncodeBuffer;
    for(;start<pEncodeBuffer+dwBufferLen-3;){
        if(memcmp((char *)start,"\x00\x00\x01",3)==0){
            //one frame
            //start+=3;
            //find the end of frame
            end = memfind(start+3,pEncodeBuffer+dwBufferLen-start-3,"\x00\x00\x01",3);
            if(end==NULL){//this is the last frame
                //HandleH264_1(start,pEncodeBuffer+dwBufferLen-start,time);
                cb(start, pEncodeBuffer+dwBufferLen-start);
                return;
            }else {//finded the end of fame
                //HandleH264_1(start,end-start,time);
                cb(start, end-start);
                start=end;
                continue;
            }
        }else{
            start++;
            continue;
        }
    }
}


JNI_API(jint,initRtspServer, jshort port){
    DebugL << port;
    int ret;
    static onceToken s_token([&](){
        ret = initRtspServer(port);
    },nullptr);
    return ret;
}


/*
 * Class:     com_wing_tools_video_ZLMediaKit
 * Method:    createMedia
 * Signature: (Ljava/lang/String;Ljava/lang/String;)J
 */
JNI_API(jlong, createMedia,jstring appName, jstring mediaName){
    auto ctx = (jlong)createMedia(jstringTostring(env,appName).c_str(), jstringTostring(env,mediaName).c_str());
    DebugL << ctx << " " << jstringTostring(env,appName).c_str() << jstringTostring(env,mediaName).c_str();
    return ctx;
}

/*
 * Class:     com_wing_tools_video_ZLMediaKit
 * Method:    releaseMedia
 * Signature: (J)V
 */
JNI_API(void,releaseMedia,jlong ctx){
    DebugL << ctx;
    MediaContext context = ((MediaContext)ctx);
    releaseMedia(context);
}

/*
 * Class:     com_wing_tools_video_ZLMediaKit
 * Method:    mediaInitVideo
 * Signature: (JIII)V
 */
JNI_API(void, mediaInitVideo,jlong ctx, jint width, jint height, jint rate){
    DebugL << ctx << " " << width << " " << height << " " << rate;
    MediaContext context = ((MediaContext)ctx);
    media_initVideo(context, width, height, rate);
}

/*
 * Class:     com_wing_tools_video_ZLMediaKit
 * Method:    mediaInitAudio
 * Signature: (JIII)V
 */
JNI_API(void,mediaInitAudio,jlong ctx, jint channel, jint sampleBit, jint smapleRate){
    DebugL << ctx << " " << channel << " " << sampleBit << " " << smapleRate;
    MediaContext context = ((MediaContext)ctx);
    media_initAudio(context, channel, sampleBit, smapleRate);
}

/*
 * Class:     com_wing_tools_video_ZLMediaKit
 * Method:    mediaInputH264
 * Signature: (J[BIJ)V
 */
JNI_API(void,mediaInputH264,jlong ctx, jbyteArray _buf, jint len, jlong stamp, jboolean allinone){
    MediaContext context = ((MediaContext)ctx);
    jbyte * olddata = (jbyte*) env->GetByteArrayElements(_buf, JNI_FALSE);
    jsize oldsize = env->GetArrayLength(_buf);

    if(allinone) {
        HandleH264((unsigned char *) olddata, (unsigned long) oldsize,
                   [&](unsigned char *dt, unsigned long size) {
                       media_inputH264(context, dt, size, stamp);
                   });
    }else{
        media_inputH264(context, olddata, oldsize, stamp);
    }

    env->ReleaseByteArrayElements(_buf, olddata, 0);
}

/*
 * Class:     com_wing_tools_video_ZLMediaKit
 * Method:    mediaInputAAC
 * Signature: (J[BIJ)V
 */
JNI_API(void, mediaInputAAC,jlong ctx, jbyteArray _buf, jint len, jlong stamp){
    MediaContext context = ((MediaContext)ctx);
    jbyte * olddata = (jbyte*) env->GetByteArrayElements(_buf, JNI_FALSE);
    jsize oldsize = env->GetArrayLength(_buf);
    media_inputAAC(context, olddata, oldsize, stamp);
    env->ReleaseByteArrayElements(_buf, olddata, 0);
}


